Skip to content

Conversation

@fiolj
Copy link
Contributor

@fiolj fiolj commented Jan 5, 2026

This PR aims to add optional arguments to savetxt, that behave similar to numpy's savetxt.

This is associated with Issue 263 and this discussion thread.

It add the possibility of supplying the unit of an open file instead of a filename (which could be used for output_unit for instance)

This implementation is quite simple. The main changes are:

  1. Add optional header and footer (that could be commented out with a character, currently defaults to '#')
  2. Changes delimiter to an arbitrary length string. I am not completely sure that is really useful. Initially I've added this because np.savetxt (Numpy's) allows character or arbitrary strings but np.loadtxt requires it to be a length 1 char.
  3. Add the possibility to override default format for saving data

@codecov
Copy link

codecov bot commented Jan 5, 2026

Codecov Report

❌ Patch coverage is 50.00000% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.61%. Comparing base (6e8669d) to head (8403d30).

Files with missing lines Patch % Lines
example/io/example_savetxt.f90 0.00% 4 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1085      +/-   ##
==========================================
- Coverage   68.62%   68.61%   -0.01%     
==========================================
  Files         394      394              
  Lines       12730    12734       +4     
  Branches     1376     1376              
==========================================
+ Hits         8736     8738       +2     
- Misses       3994     3996       +2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@jalvesz
Copy link
Contributor

jalvesz commented Jan 6, 2026

Thanks for this @fiolj. Would you mind reverting the changes not related to the implementation? (styling) There are too many and it renders difficult to read through the PR. You can check the style_guide for info https://github.com/fortran-lang/stdlib/blob/master/STYLE_GUIDE.md

One thing, white spaces in-between parentheses and an intrinsic function are not recommended ( open (...), allocate (...), etc )

@jalvesz jalvesz linked an issue Jan 6, 2026 that may be closed by this pull request
@fiolj
Copy link
Contributor Author

fiolj commented Jan 6, 2026

Thanks @jalvesz, I've fixed the formatting

Copy link
Member

@jvdp1 jvdp1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @fiolj . Here are some suggestions

`array`: Shall be a rank-2 array of type `real`, `complex` or `integer`.

`delimiter` (optional): Shall be a character expression of length 1 that contains the delimiter used to separate the columns. The default is `' '`.
`filename or unit`: Shall be either a character expression containing the name of the file or an integer containing the unit of an already open file, that will contain the 2D `array`. Setting the two of them shall give an error
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
`filename or unit`: Shall be either a character expression containing the name of the file or an integer containing the unit of an already open file, that will contain the 2D `array`. Setting the two of them shall give an error
`filename or unit`: Shall be either a character expression containing the name of the file or an integer containing the unit of an already open file, that will contain the 2D `array`. Setting the two of them shall give an error.

use stdlib_optval, only: optval
use stdlib_ascii, only: is_blank
use stdlib_string_type, only : string_type, assignment(=), move
! use stdlib_strings, only: replace_all
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
! use stdlib_strings, only: replace_all

character(len=*), intent(in), optional :: delimiter ! Column delimiter. Default is a space.
character(len=*), intent(in), optional :: header !< If present, text to write before data.
character(len=*), intent(in), optional :: footer !< If present, text to write after data.
character(len=1), intent(in), optional :: comments !< Comment character. Default "#"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it in all languages only one character? (furthermore, I don't think that there will be a check if the lenght is mentioned)

Suggested change
character(len=1), intent(in), optional :: comments !< Comment character. Default "#"
character(len=*), intent(in), optional :: comments !< Comment character. Default "#"

character(len=3) :: delim_str
character(len=:), allocatable :: default_fmt
character(len=:), allocatable :: fmt_
character(len=1), allocatable :: comments_
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
character(len=1), allocatable :: comments_
character(len=:), allocatable :: comments_

pure function prepend(Sin, comment) result(Sout)
character(len=*), intent(in) :: Sin
character(len=:), allocatable :: Sout
character(len=1), intent(in) :: comment
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
character(len=1), intent(in) :: comment
character(len=*), intent(in) :: comment

character(len=*), intent(in) :: Sin
character(len=:), allocatable :: Sout
character(len=1), intent(in) :: comment
character(len=3) :: com_
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To keep some flexibility:

Suggested change
character(len=3) :: com_
character(len=:), allocatable :: com_


s = open(filename, "w")
!! Write the header if non-empty
! prepend function may be replaced by use of replace_all but currently stdlib_strings
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is probably something that we should solve at a later stage.


`footer`: (optional) Shall be a character expression that will be written at the end of the file.

`comments`: (optional) : Shall be a character expression of length 1 that will be prepended to the ``header`` and ``footer`` strings to mark them as comments. Default: `# `.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we keep the lenght of comments undefined? (E.g., what about is a user would like to pass !# as comments?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I agree.

@fiolj
Copy link
Contributor Author

fiolj commented Jan 15, 2026

Reviewing the arguments of savetxt I've rechecked and Numpy's has the following order:
np.savetxt(fname, X, fmt, delimiter, newline, header, footer, comments, encoding)

Currently, we have the order of fmt and delimiter inverted. Should we make it as Numpy or should we keep it?

@jvdp1
Copy link
Member

jvdp1 commented Jan 19, 2026

Reviewing the arguments of savetxt I've rechecked and Numpy's has the following order: np.savetxt(fname, X, fmt, delimiter, newline, header, footer, comments, encoding)

Currently, we have the order of fmt and delimiter inverted. Should we make it as Numpy or should we keep it?

As savetxt is still experimental and as both fmt and delimiter are optional, I think that inverting both args is doable.

@fiolj
Copy link
Contributor Author

fiolj commented Jan 19, 2026

Reviewing the arguments of savetxt I've rechecked and Numpy's has the following order: np.savetxt(fname, X, fmt, delimiter, newline, header, footer, comments, encoding)
Currently, we have the order of fmt and delimiter inverted. Should we make it as Numpy or should we keep it?

As savetxt is still experimental and as both fmt and delimiter are optional, I think that inverting both args is doable.

Thanks, I agree. Last uploaded commit has the new order and some small cleanup of the code.
Following the suggestion of @certik in the thread I've searched savetxt in numpy's commit history and found no issues or complains related to these arguments.

I think that it this version is close to apt to be merged, except for the codecov failings (which I have no idea what they mean).

@jvdp1
Copy link
Member

jvdp1 commented Jan 19, 2026

xcept for the codecov failings (which I have no idea what they mean).

It is related to the examples that are not covered by these pipelines. I think that @jalvesz got a similar issue with another PR.
It should not stop us for merging this PR (provided it is not related to the changes proposed in this PR).

@jalvesz
Copy link
Contributor

jalvesz commented Jan 19, 2026

The fails with codecov are valid in my opinion. In #1074 we avoided codecov from analyzing the examples as a metric for coverage. But in this case, it is a valid fail because there are API changes which are not being tested. It is not enough to just add an example to ensure correctness.

@jvdp1
Copy link
Member

jvdp1 commented Jan 19, 2026

The fails with codecov are valid in my opinion. In #1074 we avoided codecov from analyzing the examples as a metric for coverage. But in this case, it is a valid fail because there are API changes which are not being tested.

I am confused. This shows that some lines of example_savetxt are not covered. Or do I miss-interpret the report?

It is not enough to just add an example to ensure correctness.

I agree with you.

@jvdp1
Copy link
Member

jvdp1 commented Jan 19, 2026

I think that it this version is close to apt to be merged, except for the codecov failings (which I have no idea what they mean).

@fiolj could you update the tests to cover the different changes in the API?

@fiolj
Copy link
Contributor Author

fiolj commented Jan 19, 2026

I think that it this version is close to apt to be merged, except for the codecov failings (which I have no idea what they mean).

@fiolj could you update the tests to cover the different changes in the API?

Yes. I've commited some tests.
I noticed that loadtxt is currently setting delimiter to a single char, thus I've kept the same convention to savetxt.
It is quite easy to change savetxt to arbitrary length strings; I would wait until loadtxt is also modified.

@jalvesz
Copy link
Contributor

jalvesz commented Jan 20, 2026

I am confused. This shows that some lines of example_savetxt are not covered. Or do I miss-interpret the report?

I'm also confused now, codecov is supposed to ignore files in the examples directory after the PR #1074 ... I would say for the moment to ignore this false error.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add optional arguments to savetxt

3 participants